import { Injectable } from '@nestjs/common';
import Redis from 'ioredis';

import { RedisKey } from './redis-key.factory';

@Injectable()
export class RedisService {
  constructor(private readonly redis: Redis) {}

  static key = RedisKey;

  /**
   * 存任意对象 → Redis 字符串
   * @param key   键
   * @param value 任意可序列化对象（含 BigInt/Date/Map...）
   * @param ttl   过期时间（秒）；不传则永久保存
   */
  async set<T>(key: string, value: T, ttl?: number): Promise<void> {
    const { stringify } = await import('superjson');
    const str = stringify(value);
    ttl
      ? await this.redis.setex(key, ttl, str)
      : await this.redis.set(key, str);
  }

  /**
   * 读回对象
   * @return 如果 key 不存在返回 null
   */
  async get<T>(key: string): Promise<T | null> {
    const { parse } = await import('superjson');
    const str = await this.redis.get(key);
    return str ? parse<T>(str) : null;
  }

  /** 删除一个或多个 key */
  async del(key: string): Promise<number> {
    return this.redis.del(key);
  }

  /**
   * 把“字段-值”存到 Hash 表（适合对象字段频繁读写）
   * @param key   Hash 表名
   * @param field 字段名
   * @param value 任意可序列化对象
   */
  async hset(key: string, field: string, value: unknown): Promise<number> {
    const { stringify } = await import('superjson');
    return this.redis.hset(key, field, stringify(value));
  }

  /** 读 Hash 表里某个字段 */
  async hget<T>(key: string, field: string): Promise<T | null> {
    const { parse } = await import('superjson');
    const str = await this.redis.hget(key, field);
    return str ? parse<T>(str) : null;
  }

  /**
   * 一次性批量写多个 key（pipeline 加速）
   * @param obj  key→value 的映射
   * @param ttl  统一过期时间（秒）；不传则永久
   */
  async mset(obj: Record<string, unknown>, ttl?: number): Promise<void> {
    const { stringify } = await import('superjson');
    const pipe = this.redis.pipeline();
    Object.entries(obj).forEach(([k, v]) => {
      const str = stringify(v);
      ttl ? pipe.setex(k, ttl, str) : pipe.set(k, str);
    });
    await pipe.exec();
  }

  /**
   * 限流神器：原子递增并设置过期窗口
   * @param key    限流计数器 key（如 ip:login:192.168.1.1）
   * @param window 时间窗口（秒）
   * @return       当前计数（从 1 开始）
   */
  async incrAndExpire(key: string, window = 60): Promise<number> {
    const multi = this.redis.multi();
    multi.incr(key);
    multi.expire(key, window);
    const res = await multi.exec();
    if (res === null) return this.incrAndExpire(key, window); // 乐观锁冲突重试
    const [, value] = res[0];
    return value as number;
  }
}
